#include "ThreadSessaoTest.h"

#include <QFileInfo>

namespace controller
{
	ThreadSessaoTest::ThreadSessaoTest( const QString &ip, const unsigned short &porta,
		ThreadSessaoTest *sibling, TesteCases teste, QObject *parent ) :
		QThread( parent ), porta( porta ), teste( teste ), sibling( sibling ),
		podeContinuarTeste( true )
	{
		address = QHostAddress( ip );

		podeContinuarTeste = (
			teste != getArquivoBloqueado &&
			teste != getArquivoBloquearSendoUsado &&
			teste != apagarArquivoBloqueado &&
			teste != fecharArquivoAbertoSendoUsado &&
			teste != fecharArquivoAbertoBloqueado
		);
	}

	QString* ThreadSessaoTest::getError( const char *m )
	{
		QString *s = new QString();

		s->append(
			QString( "Erro ao %1 com %2:%3 - %4")
			.arg( m, address.toString() ).arg( porta ).arg( socket->errorString() )
		);

		return s;
	}

	void ThreadSessaoTest::run()
	{
		try
		{

			socket = new QTcpSocket();
			stream = new QDataStream( socket );

			socket->connectToHost( address, porta );

			if( ! socket->waitForConnected() )
			{
				throw getError( "esperar por conectar" );
			}

			switch( teste )
			{
				case pingCliente:
					pingTeste();
					break;
				case adicionarClienteNaoExistente:
					adicionarClienteTesteSuccess();
					break;
				case adicionarClienteExistente:
					adicionarClienteTesteFail();
					break;
				case loginClienteExistente:
					loginTesteSuccess();
					break;
				case loginClienteNaoExistente:
				case loginClienteJaLogado:
					loginTesteFail( teste );
					break;
				case logoutClienteLogado:
					logoutTesteSuccess();
					break;
				case logoutClienteNaoLogado:
					logoutTesteFail();
					break;
				case adicionarArquivoNaoExistente:
					adicionarArquivoTesteSuccess();
					break;
				case adicionarArquivoExistente:
					adicionarArquivoTesteFail();
					break;
				case getArquivoExistente:
				case getArquivoBloqueiaEspera:
				case getArquivoExistenteEspera:
					getArquivoTesteSuccess( teste );
					break;
				case getArquivoNaoExistente:
				case getArquivoBloqueado:
				case getArquivoBloquearSendoUsado:
					getArquivoTesteFail( teste );
					break;
				case apagarArquivoNaoAberto:
				case apagarArquivoNaoExistente:
				case apagarArquivoBloqueado:
					apagarArquivoTesteFail( teste );
					break;
				case apagarArquivoAberto:
					apagarArquivoTesteSuccess();
					break;
				case fecharArquivoAbertoSendoUsado:
				case fecharArquivoAbertoBloqueado:
					fecharArquivoTesteSuccess( teste );
					break;
				case fecharArquivoNaoExistente:
				case fecharArquivoNaoAberto:
					fecharArquivoTesteFail( teste );
					break;
				default:
					break;
			}

			socket->disconnectFromHost();

			socket->close();

		} catch( QString *m )
		{
			if( socket->isOpen() ) socket->close();
			qDebug() << *m;
		}

		emit terminated();
	}

	void ThreadSessaoTest::waitRead( bool firstWait )
	{
		qint64 bytesToRead = 0;

		if( firstWait && socket->bytesAvailable() == 0 )
		{
			if( ! socket->waitForReadyRead() )
			{
				throw getError( "esperar para ler" );
			}
		}

		// Espera ate o long com o numero de bytes a serem enviados...
		while( (qint64) socket->bytesAvailable() < (qint64) sizeof(qint64) )
		{
			if( ! socket->waitForReadyRead() )
			{
				throw getError( "esperar para ler" );
			}
		}
		// Le o total de bytes que o outro peer quer enviar
		*stream >> bytesToRead;

		while( (qint64) socket->bytesAvailable() < bytesToRead )
		{
			if( ! socket->waitForReadyRead() )
			{
				throw getError( "esperar para ler" );
			}
		}

		array = socket->read( bytesToRead );

	}

	void ThreadSessaoTest::waitWrite()
	{
		qint64 bytesWritten = (qint64) array.size();

		*stream << bytesWritten;

		socket->flush();

		socket->write( array );

		while( (qint64) socket->bytesToWrite() < (qint64) bytesWritten )
		{
			if( ! socket->waitForBytesWritten() )
			{
				throw getError( "esperar pelo termino da escrita" );
			}
		}

		array.clear();
	}

	void ThreadSessaoTest::pingTeste()
	{

		array.push_back( toChar( ping ) );

		waitWrite();

		waitRead();
	}

	void ThreadSessaoTest::adicionarClienteTeste( const QString &nome )
	{
		try
		{
			array.push_back( toChar( cadastrarCliente ) );

			waitWrite();

			array.push_back( nome.toAscii() );

			waitWrite();

			waitRead();

			if( fromChar( *array ) == opError )
			{
				waitRead();
				throw new QString( array.data() );
			}

			qDebug() << QString( "Usuario %1 cadastrado com sucesso." ).arg( nome );
		} catch( QString *e )
		{
			qDebug() << *e;
			delete e;
		}
	}

	void ThreadSessaoTest::adicionarClienteTesteFail()
	{
		QString nome( "admin" );

		adicionarClienteTeste( nome );
	}

	void ThreadSessaoTest::adicionarClienteTesteSuccess()
	{
		QString nome( "successName" );

		adicionarClienteTeste( nome );
	}

	void ThreadSessaoTest::loginTeste( const QString &usuario )
	{

		array.push_back( toChar(login) );

		waitWrite();

		array.push_back( usuario.toAscii() );

		waitWrite();

		waitRead();

		if( fromChar(*array) == opError )
		{
			waitRead();
			throw new QString( array.data() );
		}

		qDebug() << "Sucesso ao realizar login";
		array.clear();
	}

	void ThreadSessaoTest::loginTesteFail( TesteCases caso )
	{
		QString usuario( "failName" );

		try
		{

			if( caso == loginClienteNaoExistente )
			{
				loginTeste( usuario );
			}
			else
			{
				usuario = "admin";
				loginTeste( usuario );
				loginTeste( usuario );
			}
		} catch ( QString *e )
		{
			qDebug() << *e;
			delete e;
		}
	}

	void ThreadSessaoTest::loginTesteSuccess()
	{
		QString usuario = "admin";

		try
		{
			loginTeste( usuario );
		} catch( QString *e )
		{
			qDebug() << *e;
			delete e;
		}
	}

	void ThreadSessaoTest::logoutTeste()
	{
		QString *s = new QString();

		array.push_back( toChar(logout) );

		waitRead();

		if( fromChar(*array) == opError )
		{
			waitRead();

			s->append( array.data() );

			throw s;
		} else
		{
			qDebug() << "Sucesso ao realizar login";
			array.clear();
		}

		delete s;
	}

	void ThreadSessaoTest::logoutTesteFail()
	{
		logoutTeste();
	}

	void ThreadSessaoTest::logoutTesteSuccess()
	{
		QString usuario( "admin" );

		try
		{
			loginTeste( usuario );
			logoutTeste();
		} catch( QString *e )
		{
			qDebug() << *e;
			delete e;
		}
	}

	void ThreadSessaoTest::adicionarArquivoTeste( const QString &fileName )
	{
		QFile f( fileName );

		if( ! f.exists() )
		{
			QString *s = new QString();

			s->append(
				QString( "Arquivo %1 nao existe.").arg( fileName )
			);

			throw s;
		}

		array.clear();

		array.push_back( toChar(adicionarArquivo) );

		waitWrite();

		array.push_back( fileName.toAscii() );

		waitWrite();

		// Le se o arquivo ja existe...
		waitRead();

		if( fromChar( *array ) == opError )
		{
			waitRead();
			throw new QString( array.data() );
		}

		f.open( QIODevice::ReadOnly );

		array = f.readAll();

		waitWrite();

		waitRead();

		if( fromChar( *array ) == opError )
		{
			waitRead();
			throw new QString( array.data() );
                }

                qDebug() << QString( "Arquivo %1 enviado com sucesso." ).arg( fileName );

		array.clear();
	}

	void ThreadSessaoTest::adicionarArquivoTesteFail()
	{
		QFile f( "teste.txt" );
		QString usuario( "admin" ), fileName;

		if( ! f.exists() )
		{
			f.open( QIODevice::WriteOnly );
			f.write( "Testandooo..." );
			f.close();
		}

		fileName = f.fileName();

		try
		{
			loginTeste( usuario );
			adicionarArquivoTeste( fileName );
			adicionarArquivoTeste( fileName );
		} catch( QString *e )
		{
			qDebug() << *e;
			delete e;
		}
	}

	void ThreadSessaoTest::adicionarArquivoTesteSuccess()
	{
		QFile f( "teste.txt" );
		QString usuario( "admin" ), fileName;

		if( f.exists() ) f.remove();

		f.open( QIODevice::WriteOnly );

		f.write( "Testandooo..." );
		f.close();

		fileName = f.fileName();
		try
		{
			loginTeste( usuario );
			adicionarArquivoTeste( fileName );
		} catch( QString *e )
		{
			qDebug() << *e;
			delete e;
		}
	}

	void ThreadSessaoTest::adicionarArquivosPorExtTeste( const QList<QFile*> &arquivos )
	{
		QByteArray arquivoContent;
		QFile *arquivo;
		QString fileName;


	}

	void ThreadSessaoTest::adicionarArquivosTesteFail()
	{

	}

	void ThreadSessaoTest::adicionarArquivosTesteSuccess()
	{

	}

	void ThreadSessaoTest::getArquivoTeste( const QString &nomeArquivo, model::ModoAbertura modo )
	{
		QFile *arquivo = NULL;

		array.push_back( toChar( getArquivo ) );
		waitWrite();

		array.push_back( nomeArquivo.toAscii() );
		waitWrite();

		array.push_back( modo );
		waitWrite();

		waitRead();

		if( fromChar( *array ) == opError )
		{
			waitRead();

			throw new QString( array.data() );
		} else
		{
			waitRead();
			arquivo = new QFile( nomeArquivo );

			arquivo->open( QIODevice::WriteOnly );

			arquivo->write( array );

			qDebug() <<
				QString( "Download do Arquivo %1 (%2 bytes) feito com sucesso" )
				.arg( nomeArquivo ).arg( array.size() );
		}

		array.clear();
	}

	void ThreadSessaoTest::getArquivoTesteFail( TesteCases caso )
	{
		QString usuario = "admin2";
		QString nomeArquivo = "Nao.Existe";
		model::ModoAbertura modo;

		try
		{
			loginTeste( usuario );

			switch( caso )
			{
				case getArquivoNaoExistente:
					modo = model::leitura;

					getArquivoTeste( nomeArquivo, modo );
					break;
				case getArquivoBloquearSendoUsado:
					modo = model::escrita;
					nomeArquivo = "admin.txt";

					// O Thread irma vai setar essa flag assim que abrir o arquivo para leitura
					while( ! podeContinuarTeste )
					{
						this->msleep( 500 );
					}

					getArquivoTeste( nomeArquivo, modo );

					break;
				case getArquivoBloqueado:
					modo = model::leitura;
					nomeArquivo = "admin.txt";

					// A Thread irma vai setar essa flag assim que ele bloquear o arquivo
					while( ! podeContinuarTeste )
					{
						this->msleep( 500 );
					}

					getArquivoTeste( nomeArquivo, modo );

					break;
				default:
					break;
			}
		} catch( QString *e )
		{
			qDebug() << *e;
			delete e;
		}
	}

	void ThreadSessaoTest::getArquivoTesteSuccess( TesteCases caso )
	{
		QString usuario = "admin";
		QString nomeArquivo = "admin.txt";
		model::ModoAbertura modo = model::leitura;

		try
		{
			loginTeste( usuario );
			switch( caso )
			{
				case getArquivoExistente:
					getArquivoTeste( nomeArquivo, modo );
					break;
				case getArquivoBloqueiaEspera:
					modo = model::escrita;
					getArquivoTeste( nomeArquivo, modo );

					if( sibling != NULL )
					{
						sibling->podeContinuar( true );
						// Espera pelo termino do irmao
						sibling->wait();
					}
					break;
				case getArquivoExistenteEspera:
					getArquivoTeste( nomeArquivo, modo );

					if( sibling != NULL )
					{
						sibling->podeContinuar( true );
						// Espera pelo termino do irmao
						sibling->wait();
					}
					break;
				default:
					break;
			}
		} catch ( QString *e )
		{
			qDebug() << *e;
			delete e;
		}
	}

	void ThreadSessaoTest::apagarArquivoTeste( const QString &nomeArquivo )
	{
		array.push_back( toChar( apagarArquivo ) );
		waitWrite();

		array.push_back( nomeArquivo.toAscii() );
		waitWrite();

		waitRead();

		if( fromChar( *array ) == opError )
		{
			waitRead();
			throw new QString( array.data() );
		} else
		{
			qDebug() <<
				QString( "Arquivo %1 apagado com sucesso" )
				.arg( nomeArquivo );
		}
	}

	void ThreadSessaoTest::apagarArquivoTesteFail( TesteCases caso )
	{
		QString arquivo = "admin.txt";
		QString usuario = "admin2";

		try
		{
			loginTeste( usuario );
			switch( caso )
			{
				case apagarArquivoNaoAberto:
					apagarArquivoTeste( arquivo );
					break;
				case apagarArquivoNaoExistente:
					arquivo = "Nao.existe";
					apagarArquivoTeste( arquivo );
					break;
				case apagarArquivoBloqueado: // Esse teste precisa rodar junto com o getArquivoExistenteEspera
					// Espera pelo irmao bloquear o arquivo
					while( ! podeContinuarTeste )
					{
						this->msleep( 500 );
					}
					getArquivoTeste( arquivo, model::leitura );
					apagarArquivoTeste( arquivo );
					break;
				default:
					break;
			}
		} catch( QString *e )
		{
			qDebug() << *e;
			delete e;
		}
	}

	void ThreadSessaoTest::apagarArquivoTesteSuccess()
	{
		QString usuario( "admin" );
		QString arquivo( "arquivo.txt" );
		QFile f( arquivo );

		try
		{
			loginTeste( usuario );
			if( f.exists() )
			{
				f.remove();
			}

			f.open( QIODevice::WriteOnly );

			if( ! f.isOpen() )
			{
				QString *s = new QString();

				s->append(
					QString( "Arquivo %1 nao foi aberto: %2" )
					.arg( arquivo, f.errorString() )
				);

				throw s;
			}

			f.write( "Testaaaaaando..." );
			f.close();

			adicionarArquivoTeste( arquivo );
			apagarArquivoTeste( arquivo );

		} catch( QString *e )
		{
			qDebug() << *e;
			delete e;
		}
	}

	void ThreadSessaoTest::fecharArquivoTeste( const QString &nomeArquivo )
	{
		array.push_back( toChar( fecharArquivo ) );
		waitWrite();

		array.push_back( nomeArquivo.toAscii() );
		waitWrite();

		waitRead();

		if( fromChar( *array ) == opError )
		{
			waitRead();
			throw new QString( array.data() );
		} else
		{
			qDebug() <<
				QString( "Arquivo %1 fechado com sucesso" )
				.arg( nomeArquivo );
		}
	}

	void ThreadSessaoTest::fecharArquivoTesteFail( TesteCases caso )
	{
		QString usuario( "admin2" );
		QString nomeArquivo;

		try
		{
			loginTeste( usuario );
			switch( caso )
			{
				case fecharArquivoNaoExistente:
					nomeArquivo = "Nao.Existe";
					fecharArquivoTeste( nomeArquivo );
					break;
				case fecharArquivoNaoAberto:
					nomeArquivo = "admin.txt";
					fecharArquivoTeste( nomeArquivo );
					break;
				default:
					break;
			}
		} catch( QString *e )
		{
			qDebug() << *e;
			delete e;
		}
	}

	void ThreadSessaoTest::fecharArquivoTesteSuccess( TesteCases caso )
	{
		QString usuario( "admin2" );
		QString nomeArquivo( "admin.txt" );

		try
		{
			loginTeste( usuario );

			switch( caso )
			{
				case fecharArquivoAbertoBloqueado:
					while( ! podeContinuarTeste )
					{
						this->msleep( 500 );
					}

					getArquivoTeste( nomeArquivo, model::leitura );
					fecharArquivoTeste( nomeArquivo );
					break;
				case fecharArquivoAbertoSendoUsado:
					while( ! podeContinuarTeste )
					{
						this->msleep( 500 );
					}
					getArquivoTeste( nomeArquivo, model::leitura );
					fecharArquivoTeste( nomeArquivo );
					break;
				default:
					break;
			}
		} catch( QString *e )
		{
			qDebug() << *e;
			delete e;
		}
	}

	ThreadSessaoTest::~ThreadSessaoTest()
	{
		delete stream;
		delete socket;
	}
}
